﻿'All methods are shared, but are represented as instance methods, in order to implement an interface. 
'They are accessed like shared methods via the singleton
Imports System.IO
Imports Comms.Upgrade.Client
Imports SchemaDeploy

Public Class CUpgradeServer : Implements IUpgrade

#Region "Constants"
    Public Shared MAX_HASH_AGE As New TimeSpan(0, 5, 0) 'Check files every 5-mins
#End Region


#Region "Client Factory (e.g. for testing Inproc)"
    Public Shared Function Factory() As Comms.Upgrade.Interface.IUpgrade
        Select Case Comms.Upgrade.Client.CUpgradeClient_Config.Shared.Transport
            Case ETransport.InProcess : Return New CUpgradeServer()
            Case Else : Return Comms.Upgrade.Client.CUpgradeClient.Factory
        End Select
    End Function
    Public Shared Function Factory(hostName As String, useSsl As Boolean) As Comms.Upgrade.Interface.IUpgrade
        Select Case Comms.Upgrade.Client.CUpgradeClient_Config.Shared.Transport
            Case ETransport.InProcess : Return New CUpgradeServer()
            Case Else : Return Comms.Upgrade.Client.CUpgradeClient.Factory(hostName, useSsl)
        End Select
    End Function
#End Region

#Region "Shared - Singleton"
    Private Shared m_singleton As New CUpgradeServer
    Public Shared Function [Shared]() As CUpgradeServer
        Return m_singleton
    End Function
    'Constructor
    Private Sub New()
    End Sub
#End Region

#Region "Shared - Config"
    Public Shared ReadOnly Property Config_ As CUpgradeServer_Config
        Get
            Return CUpgradeServer_Config.Shared
        End Get
    End Property
#End Region

#Region "Resolve Enum"
    Public Function ExecuteMethod(methodNameEnum As Integer, params As CReader) As Object
        Return ExecuteMethod_(methodNameEnum, params)
    End Function
    Private Function ExecuteMethod_(methodName As EUpgrade, p As CReader) As Object
		Select Case methodName
			Case EUpgrade.FirstReport : Return FirstReport(p)
			Case EUpgrade.OnAppStarted : Return OnAppStarted(p)
			Case EUpgrade.OnAppStopped
				OnAppStopped(p)
				Return Nothing

			Case Else : Throw New Exception("Unrecognised EUpgrade method: " & methodName)
		End Select
	End Function
#End Region

#Region "Parameter Casting"
	Public Function FirstReport(p As CReader) As Integer
        Return FirstReport(p.Unpack(Of CIdentity))
    End Function
    Public Function OnAppStarted(p As CReader) As CUpgradeResponse
        Return OnAppStarted(p.Unpack(Of COnAppStarted))
    End Function
    Public Sub OnAppStopped(p As CReader)
        OnAppStopped(p.Unpack(Of COnAppStopped))
    End Sub
#End Region

#Region "Native Interface (Implementation)"
	Public Function FirstReport(id As CIdentity) As Integer Implements IUpgrade.FirstReport
        Dim ins As CInstance = GetInstanceOrCreate(id)
        If IsNothing(ins) Then Return Integer.MinValue
        Return ins.InstanceId
    End Function

    Public Function OnAppStarted(info As COnAppStarted) As CUpgradeResponse Implements IUpgrade.OnAppStarted
        CloseLastReport(info.LastReportId) 'callbacks only

        'Register/Retrieve instance
        Dim i As CInstance = GetInstanceOrCreate(info.Id)

        'Store any new version/schema
        Dim r As New CUpgradeResponse()
        Dim v As CVersion = GetOrStoreVersion(info, r, i)

        'Log the event
        Dim h As New CReportHistory
        h.ReportInitialSchemaMD5 = info.SchemaMd5
        h.ReportInstanceId = i.InstanceId
        h.ReportInitialVersionId = i.TargetVersionId    'Not the actual
        If Not IsNothing(v) Then h.ReportInitialVersionId = v.VersionId
        h.Save()

        'Prepare instance label
        Dim y As New CYourInfo
		y.LastReportId = h.ReportId
		y.Id = New CIdentity(i.InstanceId, i.InstanceAppId, i.InstanceClientName, i.InstanceSuffix, i.InstanceWebHostName, CDataSrc.Default.ConnectionString)
		y.Filter = New CFilter(Config_.ExceptExtensions, True)
		r.YourName = y


        'Target version
        Dim targVer As CVersion = i.TargetVersion
        If IsNothing(targVer) Then Return r
        If Config_.DisableAutoUpgrades Then Return r

        'Target Schema
        If info.SchemaMd5 <> targVer.VersionSchemaMD5 Then
            Dim old As CBinaryFile = CBinaryFile.Cache.GetById(info.SchemaMd5)
            If IsNothing(old) Then
                r.SendSchemaInfo = True
                Return r
            End If

            Dim target As CBinaryFile = targVer.Schema

            Dim schemaOld As CSchemaInfo = CProto.Deserialise(Of CSchemaInfo)(old.GetFile())
            Dim schemaTar As CSchemaInfo = CProto.Deserialise(Of CSchemaInfo)(target.GetFile())

            Dim diff As CSchemaDiff = schemaOld.Diff(schemaTar)
            Dim scripts As New List(Of String)
            scripts.AddRange(diff.IndexDiff.ChangeScripts)
            scripts.AddRange(diff.FKDiff.ChangeScripts)
            scripts.AddRange(diff.ProcDiff.ChangeScripts)

            r.RunScripts = scripts
        End If

        'Target files
        If info.BinMd5 <> targVer.VersionFilesMD5 Then
            Dim oldVer As CVersion = CVersion.Cache.GetByFilesMD5_(info.BinMd5)

            If Not IsNothing(oldVer) Then
                r.UpgradeTo = oldVer.Diff(targVer)
            ElseIf Not IsNothing(info.Folder) Then
                r.UpgradeTo = targVer.Diff(info.Folder)
            ElseIf Not IsNothing(info.BinFiles) Then
                r.UpgradeTo = targVer.Diff(info.BinFiles)
            Else
                r.SendFolderHash = True
            End If
        End If

        Return r
    End Function

    Private Function GetOrStoreVersion(info As COnAppStarted, r As CUpgradeResponse, i As CInstance) As CVersion
        'Schema First
        Dim s As CBinaryFile = CBinaryFile.Cache.GetByIsSchema(True).GetById(info.SchemaMd5)
        If IsNothing(s) Then
            If Not IsNothing(info.Schema) Then
                s = New CBinaryFile
                s.SetFile(info.Schema)
                s.Save()
            Else
                r.SendSchemaInfo = True
            End If
        End If


        'Then Binaries
        Dim v As CVersion = CVersion.Cache.GetByFilesMD5_(info.BinMd5)
        If Not IsNothing(v) Then Return v


        Dim id As CIdentity = info.Id

        v = New CVersion()
        v.VersionAppId = id.AppId
        v.VersionExcludedFiles = info.Filters.IgnoreExtensions
		v.VersionFilesMD5 = info.BinMd5
		v.VersionName = i.NameAndSuffix
		v.VersionSchemaMD5 = info.SchemaMd5
		v.VersionTotalBytes = info.BinFiles.Total
		v.VersionUploadedFrom = String.Concat(i.IdAndName, " (", i.InstanceWebHostName, i.InstanceWebSubDir, ")")


		If IsNothing(info.BinFiles) AndAlso IsNothing(info.Folder) Then
            r.SendBinaries = True
        ElseIf Not IsNothing(info.Folder) Then 'Backwards-compat

            v.Save()

            Dim vfList As New CVersionFileList
            For Each j As CFileHash In info.Folder
                Dim bf As CBinaryFile = CBinaryFile.Cache.GetById(j.MD5)
                If IsNothing(bf) Then
                    bf = New CBinaryFile
                    bf.SetFile(j)   'No Actual file
                    bf.Save()
                End If


                Dim vf As New CVersionFile
                vf.VFVersionId = v.VersionId
                vf.VFPath = j.Name
                vf.VFBinaryMD5 = bf.MD5
                vfList.Add(vf)
            Next
            vfList.BulkInsert()
        ElseIf Not IsNothing(info.BinFiles) Then
            v.Save()

            Dim vfList As New CVersionFileList
            For Each j As CFileNameAndContent In info.BinFiles
                Dim bf As CBinaryFile = CBinaryFile.Cache.GetById(j.Md5)
                If IsNothing(bf) Then
                    bf = New CBinaryFile
                    bf.SetFile(j)
                    bf.Save()
                End If


                Dim vf As New CVersionFile
                vf.VFVersionId = v.VersionId
                vf.VFPath = j.Name
                vf.VFBinaryMD5 = bf.MD5
                vfList.Add(vf)
            Next
            vfList.BulkInsert()
        End If
        Return v
    End Function

    Public Sub OnAppStopped(info As COnAppStopped) Implements IUpgrade.OnAppStopped
        'Find it, if id was not returned
        If info.LastReportId <= 0 Then
            Dim id As CIdentity = info.Id

            Dim i As CInstance = GetInstanceOrCreate(id)
            If IsNothing(i) Then Exit Sub

            Dim l As CReportHistory = i.LastReport
            If IsNothing(l) Then Exit Sub

            info.LastReportId = l.ReportId
        End If

        CloseLastReport(info.LastReportId)
    End Sub


    Private Sub CloseLastReport(lastReportId As Integer)
        If lastReportId <= 0 Then Exit Sub

        Dim d As New CNameValueList("ReportAppStopped", DateTime.Now)
        Dim w As New CWhere(CReportHistory.TABLE_NAME, New CCriteria("ReportId", lastReportId), Nothing)

        Dim rowsAff As Integer = CDataSrc.Default.Update(d, w)
    End Sub
	Private Shared Function GetInstanceOrCreate(id As CIdentity) As CInstance
		Return GetInstanceOrCreate(id.InstanceId, id.AppId, id.ClientName, id.ClientSuffix, id.HostName, id.MachineName)
	End Function
	Private Shared Function GetInstanceOrCreate(id As Integer, appId As Integer, clientName As String, suffix As String, host As String, machine As String) As CInstance
		If 0 = CApp.Cache.Count Then Return Nothing

		'Id provided => quick
		If id > 0 Then Return CInstance.Cache.GetById(id)

		'Search by appId, name
		Dim app As CApp = CApp.Cache.GetById(appId)
		If IsNothing(app) Then app = CApp.Cache(0)

		'Search by client/suffix
		Dim ins As CInstance = app.Instances.GetByName(clientName, suffix)
		If Not IsNothing(ins) Then
			'Update the machine-name
			If IsNothing(ins.InstanceMachineName) AndAlso Not IsNothing(machine) AndAlso machine <> ins.InstanceMachineName Then
				ins.InstanceMachineName = machine
				ins.Save()
			End If
			Return ins
		End If

		'Search by machine name
		If Not String.IsNullOrEmpty(machine) Then
			ins = app.Instances.GetByMachineName_(machine)
			If IsNothing(ins) Then Return ins
		End If

		'Create new
		Dim register As New CInstance
		register.InstanceAppId = app.AppId
		register.InstanceSuffix = suffix
		register.InstanceWebHostName = host
		register.InstanceMachineName = machine
		If String.IsNullOrEmpty(clientName) Then
			register.InstanceClientName = machine
			register.InstanceClientCode = String.Empty
		Else
			register.InstanceClientName = clientName
			register.InstanceClientCode = clientName.ToUpper().Substring(0, IIf(10 > clientName.Length, clientName.Length, 10))
		End If

		register.Save()
		Return register
	End Function
#End Region





End Class